﻿using System;
using T0yK4T.Data;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using T0yK4T.Tools;
using T0yK4T.IO;
using MongoDB;
using ProtoBuf;

namespace ToyChat.Server
{
    /// <summary>
    /// A class used to "manage / compartmentalize" users
    /// </summary>
    public class User : IUser
    {
        private CryptoConnection connection;
        private IUserInfo info;
        private IServer server;
        private long totalSent;
        private long totalReceived;
        private Dictionary<int, IChannel> channels = new Dictionary<int, IChannel>();
        private int userID = -1;

        /// <summary>
        /// This event is fired when this user has been disconnected (either remotely or locally)
        /// </summary>
        public event UserDisconnected Disconnected;

        /// <summary>
        /// Initializes a new instance of <see cref="User"/> using the specified values
        /// </summary>
        /// <param name="connection">The <see cref="CryptoConnection"/> that this user is connecting through</param>
        /// <param name="server">The <see cref="IServer"/> that owns this user</param>
        /// <param name="info">The <see cref="IUserInfo"/> object that contains informations about this user</param>
        /// <param name="userID">The ID of this user</param>
        public User(CryptoConnection connection, IServer server, IUserInfo info, int userID)
        {
            this.info = info;
            this.server = server;
            this.connection = connection;
            this.connection.PacketReceived += new PacketReceivedDelegate(connection_PacketReceived);
            this.connection.Disconnected += new DisconnectedDelegate(connection_Disconnected);
            this.connection.UnSetFlag(CryptoConnectionFlags.ManualRead);
            this.userID = userID;
        }

        void connection_Disconnected(CryptoConnection which, DisconnectReason reason)
        {
            this.OnDisconnect((DisconnectType)reason);
        }

        void connection_PacketReceived(Packet packet, int wireLength)
        {
            this.totalReceived += wireLength;
            this.server.OnPacketReceived(this, packet);
        }

        /// <summary>
        /// Forcefully disconnects this user and specifies a reason (if any)
        /// </summary>
        public void Disconnect()
        {
            this.connection.Close();
            this.OnDisconnect(DisconnectType.ServerForced);
        }

        /// <summary>
        /// Attemtps to send the specified packet to this user
        /// </summary>
        /// <param name="packet"></param>
        public void Send(Packet packet)
        {
            this.totalSent += this.connection.SendPacket(packet); // Sending packet
        }

        /// <summary>
        /// Called internally when the user has disconnected
        /// </summary>
        /// <param name="type"></param>
        private void OnDisconnect(DisconnectType type)
        {
            this.server.OnDisconnect(this);
            if (this.Disconnected != null)
                this.Disconnected.Invoke(this, new DisconnectedEventArgs(type));
        }

        /// <summary>
        /// Gets or Sets the nickname of this user
        /// </summary>
        public string Nick
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the username of this user
        /// </summary>
        public string Username
        {
            get { return this.info.Username; }
        }

        /// <summary>
        /// Gets or Sets the ID of this user
        /// </summary>
        public int UserID
        {
            get { return this.userID; }
            set
            {
                if (value == -1 || value == 0)
                    throw new ArgumentOutOfRangeException("value", "value cannot be -1 or 0, -1 is reserved for server use, 0 is randomized");
                else
                    this.userID = value;
            }
        }

        /// <summary>
        /// Gets a reference to the collection of channels that this user is currently in
        /// </summary>
        public IEnumerable<IChannel> Channels
        {
            get { return this.channels.Values; }
        }

        /// <summary>
        /// Gets the status of the specified <see cref="PermissionFlags"/>
        /// </summary>
        /// <param name="flag">The flag(s) to check for</param>
        /// <returns>True if the flag is set in the <see cref="IUserInfo.Permissions"/> property of this instance, otherwise false</returns>
        private bool GetFlagStatus(PermissionFlags flag)
        {
            return (this.info.Permissions & flag) == flag;
        }

        /// <summary>
        /// Gets the <see cref="PermissionFlags"/> associated with this user
        /// </summary>
        public PermissionFlags Permissions
        {
            get { return this.info.Permissions; }
        }

        /// <summary>
        /// Gets or Sets the <see cref="IUserInfo"/> object associated with this user
        /// </summary>
        public IUserInfo Info
        {
            get { return this.info; }
            set { this.info = value; }
        }

        ///// <summary>
        ///// Gets a value indicating wether or not this user can broadcast messages to ALL users on the server
        ///// </summary>
        //public bool CanBroadcast
        //{
        //    get { return this.GetFlagStatus(PermissionFlags.CanBroadcast); }
        //}

        ///// <summary>
        ///// Gets a value indicating whether or not this user can unicast messages to a single user on the server
        ///// </summary>
        //public bool CanUnicast
        //{
        //    get { return this.GetFlagStatus(PermissionFlags.CanUnicast); }
        //}

        ///// <summary>
        ///// Gets a value indicating whether or not the user can multicast messages to multiple channels on the server
        ///// </summary>
        //public bool CanMulticast
        //{
        //    get { return this.GetFlagStatus(PermissionFlags.CanMulticast); }
        //}
    }
}
